package testhupai;

/**
 * 手牌
 *
 * @author zkpursuit
 */
public class HandCards {

    /**
     * 获取牌号最左边的一位数，如果牌为筒、条、万，则返回值为牌类型数值
     *
     * @param card 牌号
     * @return 牌号从左至右第一位数（十位数）
     */
    public final static int getCardLeftValue(int card) {
        return card / 10;
    }

    /**
     * 获取牌号最右边的一位数，如果牌为筒、条、万，则返回值为牌点数
     *
     * @param card 牌号
     * @return 牌号从右至左第一位数（个位数）
     */
    public final static int getCardRightValue(int card) {
        return card % 10;
    }

    /**
     * 获取牌号最左边的一位数，如果牌为筒、条、万，则返回值为牌类型数值
     *
     * @param idx 牌在归类数组中的索引位置
     * @return 牌号从左至右第一位数（十位数）
     */
    public final static int getCardLeftValueByClusterIndex(int idx) {
        return idx / 10 + 1;
    }

    /**
     * 获取牌号最右边的一位数，如果牌为筒、条、万，则返回值为牌点数
     *
     * @param idx 牌在归类数组中的索引位置
     * @return 牌号从右至左第一位数（个位数）
     */
    public final static int getCardRightValueByClusterIndex(int idx) {
        return idx % 10;
    }

    /**
     * 根据牌号取得其所在的牌归类数组中的索引
     *
     * @param card 牌号
     * @return 牌归类数组中的索引
     */
    public final static int getClusterIndexByCard(int card) {
        int left = getCardLeftValue(card);
        int right = getCardRightValue(card);
        int idx = (left - 1) * 10 + right;
        return idx;
    }

    /**
     * 根据十位数和个位数确定牌在聚合数组中的索引位置
     *
     * @param leftValue 十位数
     * @param rightValue 个位数
     * @return 聚合数组中的索引位置
     */
    public final static int getClusterIndex(int leftValue, int rightValue) {
        return (leftValue - 1) * 10 + rightValue;
    }

    /**
     * 归类牌<br>
     * 数组索引 / 10 + 1 表示牌类型<br>
     * 数组索引 % 10 表示牌点数<br>
     * 数组索引位置的值表示牌数量
     */
    private int[] cardClusterArray;
    /**
     * 起始有效的索引位置<br>
     * 第一个值不为0的索引位置<br>
     */
    private int startIndex;
    /**
     * 归类牌数组的有效索引位置，因为有可能后面的位置全是0<br>
     * 此索引的后续索引位置的值全部为0，即最后一个值不为0的索引位置<br>
     */
    private int lastIndex;
    /**
     * 所有的牌数量
     */
    private int cardTotals;

    /**
     * 构造方法
     */
    public HandCards() {
        cardClusterArray = new int[40];
        startIndex = 1000;
        lastIndex = -1;
        cardTotals = 0;
    }

    /**
     * 构造方法
     *
     * @param cards 未归类的牌数组
     */
    public HandCards(int[] cards) {
        this();
        if (cards != null) {
            setCards(cards);
        }
    }

    /**
     * 重置数据
     */
    public void reset() {
        if (cardTotals != 0) {
            int len = getClusterValidLength();
            for (int i = 0; i < len; i++) {
                cardClusterArray[i] = 0;
            }
        }
        startIndex = 1000;
        lastIndex = -1;
        cardTotals = 0;
    }

    /**
     * 清除数据
     */
    public void clear() {
        reset();
    }

    /**
     * 重置数据并以传入的牌数据再次初始化数据
     *
     * @param cards 牌数据
     */
    public final void setCards(int[] cards) {
        reset();
        for (int card : cards) {
            addCard(card);
        }
    }

    /**
     * 添加num张牌
     *
     * @param card 添加的牌号
     * @param num 添加的数量
     * @return true添加成功；false添加失败
     */
    public boolean addCard(int card, int num) {
        int idx = getClusterIndexByCard(card);
        int lastNum = cardClusterArray[idx] + num;
        if (lastNum > 4) {
            return false;
        }
        cardClusterArray[idx] = lastNum;
        if (idx > lastIndex) {
            lastIndex = idx;
        }
        if (idx < startIndex) {
            startIndex = idx;
        }
        cardTotals += num;
        return true;
    }

    /**
     * 添加一张牌
     *
     * @param card 牌号
     * @return true添加成功；false添加失败
     */
    public boolean addCard(int card) {
        return addCard(card, 1);
    }

    /**
     * 添加牌集合
     *
     * @param cards 牌集合，比如 [11, 23, 33, 33, 33, 34]
     * @return true添加成功，只要有一张添加失败则全部失败
     */
    public boolean addCards(int... cards) {
        for (int card : cards) {
            int idx = getClusterIndexByCard(card);
            int lastNum = cardClusterArray[idx] + 1;
            if (lastNum > 4) {
                return false;
            }
        }
        for (int card : cards) {
            addCard(card);
        }
        return true;
    }

    /**
     * 移除num张牌
     *
     * @param card 移除的牌号
     * @param num 移除的数量
     * @return true移除成功；false移除失败
     */
    public boolean removeCard(int card, int num) {
        int idx = getClusterIndexByCard(card);
        if (cardClusterArray[idx] < num) {
            return false;
        }
        cardClusterArray[idx] -= num;
        if (cardClusterArray[idx] == 0) {
            if (idx == startIndex) {
                startIndex = 1000;
                for (int i = idx; i < cardClusterArray.length; i++) {
                    if (cardClusterArray[i] > 0) {
                        startIndex = i;
                        break;
                    }
                }
            }
            if (lastIndex == idx) {
                int start = startIndex;
                if (start >= cardClusterArray.length) {
                    start = 0;
                }
                lastIndex = -1;
                for (int i = idx; i >= start; i--) {
                    if (cardClusterArray[i] > 0) {
                        lastIndex = i;
                        break;
                    }
                }
            }
        }
        cardTotals -= num;
        return true;
    }

    /**
     * 移除一张牌
     *
     * @param card 牌号
     * @return true移除成功；false移除失败
     */
    public boolean removeCard(int card) {
        return removeCard(card, 1);
    }

    /**
     * 移除牌号对应的所有牌
     *
     * @param card 牌号
     * @return true移除成功；false移除失败
     */
    public boolean removeCardOfAll(int card) {
        int num = getCardNum(card);
        if (num >= 0) {
            return removeCard(card, num);
        }
        return true;
    }

    /**
     * 移除牌
     *
     * @param cards 需要移除的牌
     * @return true表示移除成功，只要有一张牌移除失败则整个失败
     */
    public boolean removeCards(int... cards) {
        for (int card : cards) {
            int idx = getClusterIndexByCard(card);
            if (cardClusterArray[idx] < 1) {
                return false;
            }
        }
        for (int card : cards) {
            removeCard(card);
        }
        return true;
    }

    /**
     * 是否有指定的牌
     *
     * @param card 牌号
     * @return true表示存在
     */
    public boolean hasCard(int card) {
        return getCardNum(card) > 0;
    }

    /**
     * 获取牌号对应的数量
     *
     * @param card 牌号
     * @return 牌号对应的数量
     */
    public int getCardNum(int card) {
        int idx = getClusterIndexByCard(card);
        return cardClusterArray[idx];
    }

    /**
     * 获取归类的牌数据，整除10的索引位置为保留位，不参与任何实际运算<br>
     * 数组索引从0开始，有效长度（后面全部为0）结束<br>
     * 此数组为数据副本，其中的任何数据变动都不会改变原数组<br>
     * 数组索引 / 10 + 1 表示牌类型<br>
     * 数组索引 % 10 表示牌点数<br>
     *
     * @return 归类的牌数据
     */
    public int[] getCardClusterArray() {
        int[] array = new int[getClusterValidLength()];
        System.arraycopy(cardClusterArray, 0, array, 0, array.length);
        return array;
    }

    /**
     * 根据提供的索引位置获取牌数量
     *
     * @param idx 牌归类数组中的索引位置
     * @return 牌数量
     */
    public int getCardNumByClusterIndex(int idx) {
        return cardClusterArray[idx];
    }

    /**
     * 根据索引位置定位对应的牌
     *
     * @param idx 归类牌数组中的索引位置
     * @return -1表示找不到对应的牌，否则返回牌号
     */
    public int getCardByClusterIndex(int idx) {
        if (cardClusterArray[idx] <= 0) {
            return -1;
        }
        int left = getCardLeftValueByClusterIndex(idx);
        int right = getCardRightValueByClusterIndex(idx);
        return left * 10 + right;
    }

    /**
     * 归类牌数组中起始有效索引
     *
     * @return 起始有效索引，第一个值不为0的索引位置
     */
    public int getClusterValidStartIndex() {
        if (cardTotals == 0) {
            return 1;
        }
        return startIndex;
    }

    /**
     * 归类牌数组中最终的有效索引
     *
     * @return 最终有效索引，其后的值全为0
     */
    public int getClusterValidEndIndex() {
        return lastIndex;
    }

    /**
     * 归类牌数组的有效长度<br>
     * 有效的起始索引到有效的最后索引之前的长度<br>
     *
     * @return 有效长度，因为归类数组中后面可能有很多无效的0
     */
    public int getClusterValidLength() {
        return lastIndex + 1;
    }

    /**
     * 所有牌的张数
     *
     * @return 总张数
     */
    public int getCardTotals() {
        return cardTotals;
    }

    /**
     * 获取所有的牌数据，未归类
     *
     * @return 未归类的牌数据，两位数的牌号数组
     */
    public int[] getCards() {
        if (cardTotals <= 0) {
            return null;
        }
        int len = getClusterValidLength();
        int[] cards = new int[cardTotals];
        int idx = 0;
        for (int i = getClusterValidStartIndex(); i < len; i++) {
            int left = getCardLeftValueByClusterIndex(i);
            int right = getCardRightValueByClusterIndex(i);
            int count = cardClusterArray[i];
            int card = left * 10 + right;
            for (int j = 0; j < count; j++) {
                cards[idx] = card;
                idx++;
            }
        }
        return cards;
    }

    @Override
    public HandCards clone() {
        HandCards copy = new HandCards();
        copy.cardTotals = this.cardTotals;
        copy.lastIndex = this.lastIndex;
        copy.startIndex = this.startIndex;
        if (cardClusterArray != null) {
            int[] copyCardClusterArray = new int[cardClusterArray.length];
            System.arraycopy(cardClusterArray, 0, copyCardClusterArray, 0, cardClusterArray.length);
            copy.cardClusterArray = copyCardClusterArray;
        }
        return copy;
    }

}
